package com.idea.relax.redis.limiter.config;

import com.idea.relax.redis.config.RelaxRedisProperties;
import com.idea.relax.redis.limiter.aspect.RedisLimiterAspect;
import com.idea.relax.redis.limiter.client.RedisLimiterClient;
import com.idea.relax.redis.limiter.client.RedisLimiterClientImpl;
import com.idea.relax.redis.limiter.constant.RedisLimiterConstant;
import com.idea.relax.redis.limiter.reject.RedisLimiterRejectStrategy;
import com.idea.relax.redis.limiter.reject.LimiterSilenceRejectStrategy;
import com.idea.relax.redis.limiter.reject.LimiterThrowErrRejectStrategy;
import com.idea.relax.redis.support.spel.IExpressionEvaluator;
import com.idea.relax.redis.support.spel.RelaxExpressionEvaluator;
import com.idea.relax.redis.support.utils.RelaxUtil;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.util.List;
import java.util.Optional;

/**
 * @className: RedisLimiterAutoConfiguration
 * @description:
 * @author: salad
 * @date: 2022/9/6
 **/
@Order
@Configuration(
	proxyBeanMethods = false
)
@AllArgsConstructor
@EnableConfigurationProperties(RelaxRedisLimiterProperties.class)
public class RedisLimiterAutoConfiguration {

	@Bean("redisLimiterClient")
	@ConditionalOnMissingBean(name = "redisLimiterClient")
	public RedisLimiterClient redisLimiterClient(StringRedisTemplate redisTemplate) {
		return new RedisLimiterClientImpl(redisTemplate, redisLimiterScript());
	}

	@Bean("redisLimiterRejectStrategy")
	@ConditionalOnMissingBean(name = "redisLimiterRejectStrategy")
	public RedisLimiterRejectStrategy redisLimiterRejectStrategy(ObjectProvider<RedisLimiterRejectStrategy> objectProvider,
																 RelaxRedisLimiterProperties properties) {
		RedisLimiterRejectStrategy rejectStrategy;
		switch (properties.getLimiterRejectStrategy()) {
			case SILENCE:
				rejectStrategy = new LimiterSilenceRejectStrategy();
				break;
			case THROW_ERR:
				rejectStrategy = new LimiterThrowErrRejectStrategy();
				break;
			default:
				rejectStrategy = Optional
					.ofNullable(objectProvider.getIfAvailable())
					.orElseThrow(() -> new IllegalArgumentException("自定义限流的拒绝策略，需要实现 [" + RedisLimiterRejectStrategy.class.getSimpleName() + "] 接口"));
				break;
		}
		return rejectStrategy;
	}

	@Bean("redisLimiterAspect")
	public RedisLimiterAspect redisLimiterAspect(RedisLimiterClient redisLimiterClient,
												 RelaxRedisLimiterProperties properties,
												 IExpressionEvaluator expressionEvaluator,
												 RedisLimiterRejectStrategy redisLimiterRejectStrategy,
												 Environment environment) {
		return new RedisLimiterAspect(redisLimiterClient, properties, expressionEvaluator,
			resolveIsolationModeKey(properties, environment), redisLimiterRejectStrategy);
	}

	private RedisScript<Long> redisLimiterScript() {
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
		redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/limiter/redis_limiter.lua")));
		redisScript.setResultType(Long.class);
		return redisScript;
	}

	private String resolveIsolationModeKey(RelaxRedisLimiterProperties properties, Environment environment) {
		List<String> expressions = properties.getIsolationExpression();
		if (RelaxUtil.isEmpty(expressions)) {
			return null;
		}
		if (expressions.size() > RedisLimiterConstant.MAX_ISOLATION_MODE_EXPRESSION) {
			throw new IllegalArgumentException("'Isolation expression' cannot exceed " + RedisLimiterConstant.MAX_ISOLATION_MODE_EXPRESSION);
		}
		StringBuilder expressionBuffer = new StringBuilder();
		for (String expression : expressions) {
			if (RelaxUtil.isBlank(expression)) {
				throw new IllegalArgumentException("The value of the isolation expression cannot be null");
			}
			if (RelaxUtil.startEndWith(RelaxUtil.$LEFT_BRACE, expression, RelaxUtil.RIGHT_BRACE)) {
				expressionBuffer.append(environment.getProperty(expression, ""));
			} else {
				expressionBuffer.append(expression);
			}
			expressionBuffer.append(RelaxUtil.COLON);
		}
		return expressionBuffer.toString();
	}


}
